//-----------------------------------------------------------------------------
// Class:	CDynamicTerrainLoader
// Authors:	LiXizhi
// Emails:	LiXizhi@yeah.net
// Company: ParaEngine Dev Studio
// Date:	2005
//-----------------------------------------------------------------------------
#include "ParaEngine.h"
#include "DynamicTerrainLoader.h"
#include "WorldInfo.h"
#include "GlobalTerrain.h"
#include "TextureEntity.h"
#include "util/regularexpression.h"
#include "util/StringHelper.h"
#include "memdebug.h"
using namespace ParaEngine;

using namespace ParaTerrain;

/**
* fTerrainTileSize: In case a world is extremely large, it can be divided into a matrix 
* of square tiles of a given size, called terrain tile size. Terrain data can be selectively loaded
* to the game world. Remember, all world objects are created using the world coordinate system, not 
* the terrain tile's location system. if fWorldSize<=fTerrainTileSize, it is assumed that there is only
* a single terrain tile in the entire world. 
*/
CDynamicTerrainLoader::CDynamicTerrainLoader(const char* sConfigFile)
	:m_fTileSize(533.333f)
{
	LoadFromFile(sConfigFile);
}

CDynamicTerrainLoader::~CDynamicTerrainLoader(void)
{
	Cleanup();
}
/** default resolution. must be 2^n . */
#define DEFAULT_TERRAIN_MASK_TEXTURE_RESOLUTION	128

void CDynamicTerrainLoader::LoadFromFile(const char* sConfigFile)
{
	CParaFile cFile;
	cFile.OpenAssetFile(sConfigFile);
	if(cFile.isEof())
		return;

	m_sConfigFilePath = sConfigFile;
	CPathReplaceables::GetSingleton().DecodePath(m_sConfigFilePath, m_sConfigFilePath);

	CGlobals::GetWorldInfo()->ResetWorldName(sConfigFile);

	string filetype;
	if(!cFile.GetNextAttribute("type", filetype) || filetype!="lattice")
	{
		// if not a valid config file, return.
		return;
	}
	
	float TileSize;
	if(cFile.GetNextAttribute("TileSize", TileSize))
		m_fTileSize = TileSize;
	
	int nTextureMaskWidth = DEFAULT_TERRAIN_MASK_TEXTURE_RESOLUTION;
	cFile.GetNextAttribute("TextureMaskWidth", nTextureMaskWidth);
	if(Settings::GetInstance()->GetTextureMaskWidth() != nTextureMaskWidth)
	{
		Settings::GetInstance()->SetTextureMaskWidth(nTextureMaskWidth);
	}

	//char filename[MAX_PATH];
	int nX, nY;
	char line[MAX_LINE];
	string sConfigFileName;

	regex re_LineConfig("\\((.+)\\)\\s*=\\s*(.*)");
	regex re_NumberNumber("^(\\d+)\\s*,\\s*(\\d+)$");
	regex re_RangeRange("^\\[(\\d+)\\-(\\d+)\\]\\s*,\\s*\\[(\\d+)\\-(\\d+)\\]$");
	while(cFile.GetNextLine(line, MAX_LINE)>0)
	{
		// parse "(%d,%d) = %s"
		smatch result;
		string sLine = line;
		if (regex_search(sLine, result, re_LineConfig))
		{
			string sCoordinates = result.str(1);
			string sFilename = result.str(2);

			int nX_From, nY_From, nX_To, nY_To;
			nX_From = -1;
			if (regex_search(sCoordinates, result, re_NumberNumber))
			{
				std::string nX_From_s = result.str(1);
				std::string nY_From_s = result.str(2);
				nX_From = StringHelper::StrToInt(nX_From_s.c_str());
				nY_From = StringHelper::StrToInt(nY_From_s.c_str());
				nX_To = nX_From;
				nY_To = nY_From;
			}
			else if (regex_search(sCoordinates, result, re_RangeRange))
			{
				std::string nX_From_s = result.str(1);
				std::string nX_To_s = result.str(2);
				std::string nY_From_s = result.str(3);
				std::string nY_To_s = result.str(4);
				nX_From = StringHelper::StrToInt(nX_From_s.c_str());
				nX_To = StringHelper::StrToInt(nX_To_s.c_str());
				nY_From = StringHelper::StrToInt(nY_From_s.c_str());
				nY_To = StringHelper::StrToInt(nY_To_s.c_str());
			}
			else
			{
				nX_From = -1;
			}

			if(nX_From>=0)
			{
				for (nX=nX_From; nX<=nX_To;++nX)
				{
					for (nY=nY_From;nY<=nY_To;++nY)
					{
						int nTileID = TerrainLattice::GetTileIDFromXY(nX, nY);
						m_TerrainTiles[nTileID] = TerrainTileInfo(sFilename.c_str());
					}
				}
			}
		}
	}
}

bool CDynamicTerrainLoader::UpdateTileConfigFile(int x, int y, const string& sTileConfigFile)
{
	int nID = TerrainLattice::GetTileIDFromXY(x, y);
	if(!sTileConfigFile.empty())
	{
		map<int, TerrainTileInfo>::iterator iter = m_TerrainTiles.find(nID);
		if(iter!=m_TerrainTiles.end())
		{
			(*iter).second.m_sConfigFileName = sTileConfigFile;
		}
		else
		{
			m_TerrainTiles[nID] = TerrainTileInfo(sTileConfigFile.c_str());
		}
	}
	else
	{
		m_TerrainTiles.erase(nID);
	}
	return true;
}

bool CDynamicTerrainLoader::SaveWorldConfigFile()
{
	CParaFile cFile;
	
	if(cFile.CreateNewFile(m_sConfigFilePath.c_str(), true) == false)
		return false;
	char line[MAX_LINE+1];
	memset(line, 0, sizeof(line));
	cFile.WriteString("-- Auto generated by ParaEngine \n");
	cFile.WriteString("type = lattice\n");
	snprintf(line, MAX_LINE, "TileSize = %f\n", m_fTileSize);
	cFile.WriteString(line);
	
	if(CGlobals::GetGlobalTerrain()->GetTextureMaskWidth() != DEFAULT_TERRAIN_MASK_TEXTURE_RESOLUTION)
	{
		snprintf(line, MAX_LINE, "TextureMaskWidth = %d\n", CGlobals::GetGlobalTerrain()->GetTextureMaskWidth());
		cFile.WriteString(line);
	}

	int nMinX,nMinY;
	TerrainLattice::GetXYFromTileID((m_TerrainTiles.begin())->first, &nMinX, &nMinY);
	int nMaxX = nMinX;
	int nMaxY = nMinY;
	map<int, TerrainTileInfo>::iterator iter, iterEnd = m_TerrainTiles.end();
	for (iter = m_TerrainTiles.begin(); iter!=iterEnd; ++iter)
	{
		if((*iter).second.m_bIsValid != false)
		{
			int x1,y1;
			TerrainLattice::GetXYFromTileID((*iter).first, &x1, &y1);
			nMinX = min(nMinX, x1);
			nMinY = min(nMinY, y1);
			nMaxX = max(nMaxX, x1);
			nMaxY = max(nMaxY, y1);
		}
	}

	int nFromX = nMinX;
	int nFromY = nMinY;
	int nToX = nMinX;
	int nToY = nMinY;
	string sConfig;
	string sLastConfig;
	for(int nX=nMinX; nX<=nMaxX; ++nX)
	{
		for(int nY=nMinY; nY<=nMaxY; ++nY)
		{
			int nID = TerrainLattice::GetTileIDFromXY(nX, nY);
			iter = m_TerrainTiles.find(nID);
			if(iter!=iterEnd)
			{
				sConfig = (*iter).second.m_sConfigFileName;
				
				if(CGlobals::GetGlobalTerrain()->GetEnablePathEncoding())
				{
					CPathReplaceables::GetSingleton().EncodePath(sConfig, sConfig);
				}
			}
			else
			{
				sConfig.clear();
			}

			if(sConfig != sLastConfig || (nY==nMaxY && nX==nMaxX))
			{
				// export longest sequence with the same config file. 
				nToX = nX;
				if(nY!=nMaxY || nX!=nMaxX)
				{
					nToY = nY-1;
				}
				else
				{
					nToY = nMaxY;
				}
				if(nToY<nMinY)
				{
					nToX -= 1;
					nToY = nMaxY;
				}
				if(nToX>=0 && !sLastConfig.empty())
				{
					if(nToX == nFromX && nToY==nFromY)
					{
						snprintf(line, MAX_LINE, "(%d,%d) = %s\n", nToX,nToY, sLastConfig.c_str());
						cFile.WriteString(line);
					}
					else
					{
						// _________OOOOOOOOOOO
						// 00000000000000000000
						// 00000_______________
						if(nFromY>nMinY)
						{
							if(nToX == nFromX)
							{
								snprintf(line, MAX_LINE, "([%d-%d],[%d-%d]) = %s\n", nFromX, nFromX, nFromY, nToY, sLastConfig.c_str());
								cFile.WriteString(line);
							}
							else
							{
								snprintf(line, MAX_LINE, "([%d-%d],[%d-%d]) = %s\n", nFromX, nFromX, nFromY, nMaxY, sLastConfig.c_str());
								cFile.WriteString(line);
							}
							nFromX += 1;
							nFromY = nMinY;
						}
						if(nFromX <= nToX)
						{
							if(nToY < nMaxY)
							{
								snprintf(line, MAX_LINE, "([%d-%d],[%d-%d]) = %s\n", nToX, nToX, 0, nToY, sLastConfig.c_str());
								cFile.WriteString(line);
								nToX -= 1;
								nToY = nMaxY;
							}
							if(nFromX <= nToX)
							{
								snprintf(line, MAX_LINE, "([%d-%d],[%d-%d]) = %s\n", nFromX, nToX, nFromY, nToY, sLastConfig.c_str());
								cFile.WriteString(line);
							}
						}
					}
				}

				// restart next
				sLastConfig = sConfig;
				nFromX = nX;
				nFromY = nY;
			}
		}
	}
	return true;
}

void CDynamicTerrainLoader::Cleanup()
{
}
void CDynamicTerrainLoader::UnloadTerrain(int latticeX, int latticeY, Terrain * pTerrain)
{
}

Terrain * CDynamicTerrainLoader::LoadTerrainAt(Terrain *pTerrain, int latticeX, int latticeY,bool useGeoMipmap)
{
	if(pTerrain == NULL)
	{
		pTerrain = new Terrain();
		pTerrain->m_useGeoMipmap = useGeoMipmap;
	}
	else
		pTerrain->Cleanup();
	pTerrain->SetLatticePosition(latticeX, latticeY);
	pTerrain->SetOffset(latticeX * GetTerrainWidth(), latticeY * GetTerrainHeight());
	
	map<int, TerrainTileInfo>::iterator iter = m_TerrainTiles.find(TerrainLattice::GetTileIDFromXY(latticeX, latticeY));
	if(iter != m_TerrainTiles.end())
	{
		if((*iter).second.m_bIsValid)
		{
			// override the size read from the file.
			(*iter).second.m_bIsValid = pTerrain->LoadFromConfigFile((*iter).second.m_sConfigFileName.c_str(), m_fTileSize, m_sConfigFilePath.c_str());
		}
	}
	return pTerrain;
}